#include <sys/socket.h>
#include <sys/types.h>

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <arpa/inet.h>
#include <iostream>

#include "SSocket.h"

#define LOGGER_NAME "shadow.sscf.ssocket"

using std::cout;
using std::endl;

SSocket::SSocket() {
	_is_connected = false;
	_fd = -1;
	_last_data_input_time = -1;
	_last_data_output_time = -1;
	_connected_time = -1;
	_index = -1;
	_last_send_fail_time = -1;
	_last_recv_fail_time = -1;
	_logger = log4cxx::Logger::getLogger(LOGGER_NAME);
}

SSocket::~SSocket() {
}

int SSocket::set_fd(int fd) {
	if (_is_connected) {
		LOG4CXX_ERROR(_logger, "set a fd to a already connected socket, in use fd:" << _fd << ", to set fd:" << fd);
		return -1;
	}
	if (fd < 1) {
		LOG4CXX_ERROR(_logger, "set invalid fd to the socket, fd:" << fd);
		return -2;
	}
	_fd = fd;
	_is_connected = true;
	return true;
}

int SSocket::set_connected_time(time_t connected_time) {
		_connected_time = connected_time;
	       return 1;
}	       

int SSocket::set_to_nonblock(int & old_socket_flag) {
	int flag = fcntl(_fd, F_GETFL, 0);
	if (flag < 0) {
		LOG4CXX_ERROR(_logger, "can not get socket flag, fd:" << _fd << ", res:" << flag);
		return -1;
	}
	old_socket_flag = flag;
	int res = fcntl(_fd, F_SETFL, flag | O_NONBLOCK);
	if (-1 == res) {
		LOG4CXX_ERROR(_logger, "can not set socket flag to nonblock, fd:" << _fd << ", flag:" << (flag | O_NONBLOCK));
		return -2;
	}
	return 1;
}
int SSocket::set_flag(int flag) {
	int res = fcntl(_fd, F_SETFL, flag);
	if (-1 == res) {
		LOG4CXX_ERROR(_logger, "can not set socket flag fd:" << _fd << ", flag:" << flag);
		return -2;
	}
	return 1;
}

int SSocket::send_all(const char * buf, const int len, int timeout_in_sec) {
	if (!_is_connected) {
		LOG4CXX_ERROR(_logger, "send data through a not connected socket, fd:" << _fd);
		return -1;
	}
	if (NULL == buf || 0 == len) {
		LOG4CXX_ERROR(_logger, "send null or zero len buffer, fd:" << _fd << ", len:" << len);
		return -2;
	}
	int old_flag;
	if (set_to_nonblock(old_flag) < 0) {
			_last_send_fail_time = time_t(NULL);
			return -3;
	}
	int total_sent = 0;
	fd_set writefds;
	FD_ZERO(&writefds);
	FD_SET(_fd, &writefds);
	struct timeval tv;
	struct timeval * ptv(NULL);
	if (timeout_in_sec > 0) ptv = &tv;
	while (total_sent < len) { 
		tv.tv_sec = timeout_in_sec;
		tv.tv_usec = 0;
		int res = select(_fd + 1, NULL, &writefds, NULL, ptv);
		if (res < 0) {
			_last_send_fail_time = time_t(NULL);
			LOG4CXX_ERROR(_logger, "select write error, res:" << res << ", fd:" << _fd);
			return -4;
		}
		else if (0 == res) {
			_last_send_fail_time = time_t(NULL);
			LOG4CXX_ERROR(_logger, "socket send timeout, fd:" << _fd << ", total sent:" << \
					total_sent << ", total_len:" << len);
			return -5;
		}
		int s_error;
		res = get_sock_opt_error(s_error);
		if (res < 0) {
				_last_send_fail_time = time_t(NULL);
				return -6;
		}
		if (0 != s_error) {
				_last_send_fail_time = time_t(NULL);
				LOG4CXX_ERROR(_logger, "send fail, socket has error condition, fd:" << _fd << ", error:" << s_error);
				return -7;
		}
		ssize_t send = ::send(_fd, buf + total_sent, len - total_sent, 0);
		if (send >= 0) {
			total_sent += send;
			continue;
		}
		else if (EAGAIN == errno || EWOULDBLOCK == errno) {
			continue;
		}
		else {
			_last_send_fail_time = time_t(NULL);
			LOG4CXX_ERROR(_logger, "socket send error, fd:" << _fd << ", errno:" << errno << \
					", total_sent:" << total_sent << ", total_len:" << len);
			return -8;
		}
	}
	set_last_data_output_time(time(NULL));
	set_flag(old_flag);
	return len;
}

int SSocket::recv_all(string & buf, const int len, int timeout_in_sec) {
	if (!_is_connected) {
		LOG4CXX_ERROR(_logger, "recv data through a not connected socket, fd:" << _fd);
		return -1;
	}
	if (len < 0) {
		LOG4CXX_ERROR(_logger, "recv zero len buffer, fd:" << _fd);
		return -2;
	}
	int old_flag;
	if (set_to_nonblock(old_flag) < 0) {
			_last_recv_fail_time = time(NULL);	
			return -3;
	}
	int total_recv = 0;
	fd_set readfds;
	FD_ZERO(&readfds);
	FD_SET(_fd, &readfds);
	struct timeval tv;
	struct timeval * ptv(NULL);
	if (timeout_in_sec > 0) ptv = &tv;
	char * t = new char[len];
	buf.clear();
	while (total_recv < len) { 
		tv.tv_sec = timeout_in_sec;
		tv.tv_usec = 0;
		int res = select(_fd + 1, &readfds, NULL, NULL, ptv);
		if (res < 0) {
			_last_recv_fail_time = time(NULL);	
			LOG4CXX_ERROR(_logger, "select recv error, res:" << res << ", fd:" << _fd);
			delete[] t;
			return -3;
		}
		else if (0 == res) {
			_last_recv_fail_time = time(NULL);	
			LOG4CXX_ERROR(_logger, "socket recv timeout, fd:" << _fd << ", total sent:" << \
					total_recv << ", total_len:" << len);
			delete[] t;
			return -4;
		}
		ssize_t recv = ::recv(_fd, t, len, 0);
		if (recv > 0) {
			total_recv += recv;
			buf.append(t, recv);
			continue;
		}
		else if (0 == recv) {
			delete[] t;	
			return 0;
		}
		else if (EAGAIN == errno || EWOULDBLOCK == errno) {
			continue;
		}
		else {
			_last_recv_fail_time = time(NULL);	
			LOG4CXX_ERROR(_logger, "socket recv error, fd:" << _fd << ", errno:" << errno << \
					", total_recv:" << total_recv << ", total_len:" << len);
			delete[] t;
			return -5;
		}
	}
	set_last_data_input_time(time(NULL));
	set_flag(old_flag);
	delete[] t;
	return 1;
}

int SSocket::recv_any(string & buf, int timeout_in_sec) {
	if (!_is_connected) {
		LOG4CXX_ERROR(_logger, "recv data through a not connected socket, fd:" << _fd);
		return -1;
	}
	int old_flag;
	if (set_to_nonblock(old_flag) < 0) {
			_last_recv_fail_time = time(NULL);
			return -2;
	}
	fd_set readfds;
	FD_ZERO(&readfds);
	FD_SET(_fd, &readfds);
	struct timeval tv;
	struct timeval * ptv(NULL);
	if (timeout_in_sec > 0) ptv = &tv;
	char t[DEFAULT_RECV_BUFFER_SIZE];
	buf.clear();
	while (true) { 
		tv.tv_sec = timeout_in_sec;
		tv.tv_usec = 0;
		int res = select(_fd + 1, &readfds, NULL, NULL, ptv);
		if (res < 0) {
			_last_recv_fail_time = time(NULL);	
			LOG4CXX_ERROR(_logger, "select recv error, res:" << res << ", fd:" << _fd);
			return -3;
		}
		else if (0 == res) {
			_last_recv_fail_time = time(NULL);	
			LOG4CXX_ERROR(_logger, "socket recv timeout, fd:" << _fd );
			return -4;
		}
		while (true) {
			ssize_t recv = ::recv(_fd, t, DEFAULT_RECV_BUFFER_SIZE, 0);
			if (recv > 0) {
				set_last_data_input_time(time(NULL));
				buf.append(t, recv);
				continue;
			}
			else if (0 == recv) return 0;
			else if (EAGAIN == errno || EWOULDBLOCK == errno) {
				if (buf.size() > 0) {
					 set_flag(old_flag);
					 return 1;
				}
				else break;
			}
			else {
				_last_recv_fail_time = time(NULL);	
				LOG4CXX_ERROR(_logger, "socket recv error, fd:" << _fd << ", errno:" << errno);	
				return -5;
			}
		}
	}
	_last_recv_fail_time = time(NULL);	
	return -6;
}

int SSocket::connect(const char * ip, int port, int timeout_in_sec) {
	if (NULL == ip || port < 1) {
		LOG4CXX_ERROR(_logger, "connect ip or port invalid, port:" << port << ", fd:" << _fd);
		return -1;
	}
	if (_is_connected) {
		LOG4CXX_ERROR(_logger, "connect a to new desination through a already connected socket, fd:" << _fd << \
						", ip:" << ip << ", port:" << port);
		return -2;
	}
	in_addr_t inaddr = inet_addr(ip);
	if ((in_addr_t)(-1) == inaddr) {
		LOG4CXX_ERROR(_logger, "connect error, convert ip address from string form to integer form error, ip:" \
				<< ip);
		return -3;
	}
	_fd = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == _fd) {
		LOG4CXX_ERROR(_logger, "create socket error, errno:" << errno << ", fd:" << _fd << ", ip:" << ip << ", port:" << \
						port);
		return -3;
	}
	struct sockaddr_in addr;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = inaddr;
	addr.sin_family = AF_INET;
	int old_flag;
	int res = set_to_nonblock(old_flag);
	if (res < 0) {
		::close(_fd);
		_fd = -1;
		return -4;
	}
	res = ::connect(_fd, (struct sockaddr *)&addr, sizeof(addr));
	if (0 == res) {
		_connected_time = time(NULL);
		_is_connected = true;
		set_flag(old_flag);
		return 1;
	}
	else if (EINPROGRESS == errno) {
		fd_set writefds;
		FD_ZERO(&writefds);
		FD_SET(_fd, &writefds);
		struct timeval tv;
		struct timeval * ptv(NULL);
		if (timeout_in_sec > 0) ptv = &tv;
		tv.tv_sec = timeout_in_sec;
		tv.tv_usec = 0;
		res = select(_fd + 1, NULL, &writefds, NULL, ptv);
		if (res < 0) {
			LOG4CXX_ERROR(_logger, "connect error, select write error, res:" << res << ", fd:" << _fd << ", ip:" << ip << \
							", port:" << port);
			::close(_fd);
			_fd = -1;
			return -5;
		}
		else if (0 == res) {
			LOG4CXX_ERROR(_logger, "connect error, socket connect timeout, fd:" << _fd << ", ip:" << ip << ", port:" << \
							port << ", timeout:" << timeout_in_sec);
			::close(_fd);
			_fd = -1;
			return -6;
		}
		int socket_error;
		socklen_t len = sizeof(socket_error);
		res = getsockopt(_fd, SOL_SOCKET, SO_ERROR, &socket_error, &len);
		if (0 != res || 0 != socket_error) {
				LOG4CXX_ERROR(_logger, "on connect, get socket opt(SO_ERROR) error, fd:" << _fd << ", res:" << res << \
								", errno:" << errno << ", socket_error:" << socket_error << ", ip:" << ip << ", port:" << \
								port);
				::close(_fd);
				_fd = -1;
				return -7;
		}
		_connected_time = time(NULL);
		_is_connected = true;
		set_flag(old_flag);
		return 1;
	}
	LOG4CXX_ERROR(_logger, "connect error, erron:" << errno << ", ip:" << ip << ", port:" << port);
	::close(_fd);
	_fd = -1;
	return -8;
}

int SSocket::listen(int port) {
	if (port < 0) {
		LOG4CXX_ERROR(_logger, "listen error, port invalid:" << port << ", fd:" << _fd );
		return -1;
	}
	if (_is_connected) {
		LOG4CXX_ERROR(_logger, "try to listen on a connected socket, fd:" << _fd);
		return -2;
	}
	_fd = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == _fd) {
		LOG4CXX_ERROR(_logger, "create socket error, errno:" << errno << ", fd:" << _fd);
		return -3;
	}
	int reuse_add_flag(1);
	int res = setsockopt(_fd, SOL_SOCKET, SO_REUSEADDR, &reuse_add_flag, sizeof(reuse_add_flag));
	if (0 != res) {
		::close(_fd);
		_fd = -1;
		LOG4CXX_ERROR(_logger, "set socket opt to reuse addr error, errno:" << errno << ", fd:" << _fd);
		return -4;
	}
	struct sockaddr_in addr;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = htonl(INADDR_ANY);
	addr.sin_family = AF_INET;
	res = bind(_fd, (struct sockaddr *)&addr, sizeof(addr));
	if (0 != res) {
		::close(_fd);
		_fd = -1;
		LOG4CXX_ERROR(_logger, "bind error, errno:" << errno << ", fd:" << _fd);
		return -5;
	}
	res = ::listen(_fd, DEFAULT_LISTEN_BACKLOG);
	if (0 != res) {
		::close(_fd);
		_fd = -1;
		LOG4CXX_ERROR(_logger, "listen error, errno:" << errno << ", fd:" << _fd);
		return -6;
	}
	_is_connected = true;
	_connected_time = time(NULL);
	LOG4CXX_INFO(_logger, "socket successfully listen on port:" << port << ", fd:" << _fd);
	return 1;
}

int SSocket::accept(SSocket & new_socket) {
	if (!_is_connected) {
		LOG4CXX_ERROR(_logger, "try to accept from a not in listen state socket, fd:" << _fd);
		return -1;
	}
	int new_fd = ::accept(_fd, NULL, NULL);
	if (new_fd < 1) {
		LOG4CXX_ERROR(_logger, "accept error, errno:" << errno << ", fd:" << _fd);
		return -2;
	}
	new_socket.set_fd(new_fd);
	new_socket.set_connected_time(time(NULL));
	return 1;
}


int SSocket::get_fd() { return _fd; }

time_t SSocket::get_connected_time() { return _connected_time; }

time_t SSocket::get_last_data_input_time() { return _last_data_input_time; }

time_t SSocket::get_last_data_output_time() { return _last_data_output_time; }

bool SSocket::is_connected() { return _is_connected; }

int SSocket::close() {
	if (!_is_connected) return -1;
	::close(_fd);
	_is_connected = false;
	_fd = -1;
	_connected_time = -1;
	_last_data_input_time = -1;
	_last_data_output_time = -1;
	return 1;
}

int SSocket::set_last_data_output_time(time_t t) {
	_last_data_output_time = t;
	return 1;
}

int SSocket::set_last_data_input_time(time_t t) {
	_last_data_input_time = t;
	return 1;
}

int SSocket::get_index() { return _index; }

int SSocket::set_index(int index) { _index = index; return 1; }

int SSocket::is_writetable(bool & is_writable, int timeout_in_sec) {
		if (!_is_connected) {
				LOG4CXX_ERROR(_logger, "check socket writable through a not connected socket, fd:" << _fd);
		return -1;
	}
	fd_set writefds;
	FD_ZERO(&writefds);
	FD_SET(_fd, &writefds);
	struct timeval tv;
	tv.tv_sec = timeout_in_sec;
	tv.tv_usec = 0;
	struct timeval * ptv(NULL);
	if (timeout_in_sec > 0) ptv = &tv;
	int res = select(_fd + 1, NULL, &writefds, NULL, ptv);
	if (res < 0) {
			LOG4CXX_ERROR(_logger, "in check socket writable, select write error, res:" << res << ", fd:" << _fd);
			return -2;
	}
	else if (0 == res) {
			LOG4CXX_ERROR(_logger, "in check socket writable, socket select timeout, fd:" << _fd); 
			is_writable = false;
			return 1;
	}
	is_writable = true;
	return 2;
}

time_t SSocket::get_last_recv_fail_time() { return _last_recv_fail_time; }

time_t SSocket::get_last_send_fail_time() { return _last_send_fail_time; }

int SSocket::get_sock_opt_error(int & s_error) {
		if (!_is_connected) {
				LOG4CXX_ERROR(_logger, "try to get socket opt from a not connected socket, fd:" << _fd );
				return -1;
		}
		socklen_t len = sizeof(s_error);
		int res = getsockopt(_fd, SOL_SOCKET, SO_ERROR, &s_error, &len);
		if (0 != res) {
				LOG4CXX_ERROR(_logger, "get socket opt(SO_ERROR) error, fd:" << _fd << ", res:" << res << \
								", errno:" << errno); 
				return -2;
		}
		if (0 != s_error) {
				LOG4CXX_WARN(_logger, "got socket opt(SO_ERROR) value:" << s_error << ", fd:" << _fd);
				return -3;
		}
		return 1;
}
